Skip to main content

Next.js/React Team Development

Let's be honest: if you're building a Next.js app by yourself, you probably don't need this. Environment variables in Next.js work fine out of the box, and you can manage your .env.local file just fine.

But if you're working with a team, you've probably hit these problems:

Someone deploys to production with NEXTAUTH_URL=http://localhost:3000 and authentication breaks. A new developer joins and spends half a day figuring out why OAuth isn't working (spoiler: they're missing environment variables that aren't in the example file). Your staging environment is using test Stripe keys when it should be using staging keys.

These aren't Next.js problems. They're team coordination problems.

When This Actually Helps

You probably need this if:

You have a team - Solo developers can manage .env files just fine
Multiple environments - dev/staging/production with different URLs and keys
New team members regularly join and need to get up and running
You've been bitten by deployment config errors - wrong URLs, test keys in production
Your .env.example file is always out of date - because someone forgot to update it

Skip this if:

❌ You're working alone - just use regular .env.local files
❌ You only have one environment that never changes
❌ Your team is stable and everyone knows the setup by heart

What Actually Goes Wrong

The classic Next.js team development problems:

The OAuth Mystery: New developer joins, copies .env.example to .env.local, but OAuth doesn't work. Turns out the example file is missing three environment variables that got added last month. No one remembers to update example files.

The Deployment Surprise: Everything works locally, deploy to staging, and authentication breaks. NEXTAUTH_URL is still pointing to localhost:3000. This happens more often than you'd think.

The Stripe Incident: Production is accidentally using test API keys, or worse, staging is using live keys and charging real customers for test orders.

The Onboarding Crawl: New team member spends their first day setting up the development environment. Half the instructions are outdated, they're missing database credentials, and they don't know which OAuth apps to create.

How Axogen Helps

Axogen doesn't solve Next.js complexity—it solves team coordination complexity:

Environment validation: Catch wrong URLs and invalid API keys before deployment, not after.

Always-accurate example files: Your .env.example is generated from the same source as your real config. It can't be wrong.

Team onboarding automation: New developers run one command and get step-by-step setup with validation.

Multi-environment coordination: Automatically adjust URLs and API endpoints based on where you're deploying.

A Real Example

Here's what a practical Next.js team configuration looks like:

// axogen.config.ts
import {defineConfig, loadEnv, env, cmd, liveExec} from "@axonotes/axogen";
import * as z from "zod";

const envVars = loadEnv(
z.object({
NODE_ENV: z
.enum(["development", "staging", "production"])
.default("development"),

// Database
DATABASE_URL: z.url("Database URL must be valid"),

// NextAuth - the most common deployment failure
NEXTAUTH_SECRET: z
.string()
.min(32, "NextAuth secret must be at least 32 characters"),

// OAuth
GOOGLE_CLIENT_ID: z.string().min(1, "Google Client ID is required"),
GOOGLE_CLIENT_SECRET: z
.string()
.min(1, "Google Client Secret is required"),

// API keys with validation
STRIPE_PUBLISHABLE_KEY: z
.string()
.startsWith("pk_", "Stripe publishable key must start with pk_"),
STRIPE_SECRET_KEY: z
.string()
.startsWith("sk_", "Stripe secret key must start with sk_"),
})
);

// Environment-aware URL generation
const getNextAuthUrl = () => {
switch (envVars.NODE_ENV) {
case "development":
return "http://localhost:3000";
case "staging":
return "https://staging.myapp.com";
case "production":
return "https://myapp.com";
}
};

export default defineConfig({
targets: {
app: env({
path: ".env.local",
variables: {
DATABASE_URL: envVars.DATABASE_URL,
NEXTAUTH_URL: getNextAuthUrl(),
NEXTAUTH_SECRET: envVars.NEXTAUTH_SECRET,

GOOGLE_CLIENT_ID: envVars.GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET: envVars.GOOGLE_CLIENT_SECRET,

// Public variables (NEXT_PUBLIC_ prefix)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY:
envVars.STRIPE_PUBLISHABLE_KEY,

// Server-only variables
STRIPE_SECRET_KEY: envVars.STRIPE_SECRET_KEY,
},
}),

// Perfect example file - always accurate
example: env({
path: ".env.example",
variables: {
DATABASE_URL: "postgresql://user:password@localhost:5432/myapp",
NEXTAUTH_SECRET: "your-32-character-secret-goes-here",
GOOGLE_CLIENT_ID: "your-google-client-id",
GOOGLE_CLIENT_SECRET: "your-google-client-secret",
STRIPE_PUBLISHABLE_KEY: "pk_test_your_publishable_key",
STRIPE_SECRET_KEY: "sk_test_your_secret_key",
},
}),
},

commands: {
"team-setup": cmd({
help: "Get new team members up and running",
exec: async () => {
console.log("🚀 Setting up development environment...");

await liveExec("npm install");

try {
await liveExec("npx prisma generate");
await liveExec("npx prisma db push");
console.log("✅ Database ready");
} catch (error) {
console.log(
"⚠️ Database setup failed - check your DATABASE_URL"
);
}

console.log("\n✅ Setup complete!");
console.log(
"Next: Copy .env.example to .env.local and update values"
);
},
}),

"pre-deploy": cmd({
help: "Validate environment before deployment",
exec: async () => {
console.log("🔍 Validating deployment environment...");

if (envVars.NODE_ENV === "production") {
if (getNextAuthUrl().includes("localhost")) {
throw new Error(
"❌ NEXTAUTH_URL still points to localhost in production!"
);
}

if (envVars.STRIPE_SECRET_KEY.startsWith("sk_test_")) {
throw new Error(
"❌ Using test Stripe keys in production!"
);
}
}

console.log("✅ Environment validation passed");
},
}),
},
});

Your .env.axogen file (keep this secret):

NODE_ENV=development

DATABASE_URL=postgresql://postgres:mypassword@localhost:5432/myapp_dev
NEXTAUTH_SECRET=my-super-secret-32-character-key-here
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
STRIPE_PUBLISHABLE_KEY=pk_test_abcd1234
STRIPE_SECRET_KEY=sk_test_abcd1234

What Changes

New team member onboarding:

git clone your-app
cd your-app
axogen run team-setup # Automated setup with validation
# Copy .env.example to .env.local and fill in values
npm run dev # Everything works

Deployment validation:

NODE_ENV=production axogen run pre-deploy  # Catches config errors before deploy

Always-accurate documentation: Your .env.example file is generated from the same schema as your real config. When you add a new environment variable, both files update together.

Environment-specific URLs: Your NEXTAUTH_URL automatically adjusts based on where you're deploying. No more manual URL updates.

The Bottom Line

This isn't about Next.js being hard to configure—it's about teams staying coordinated. Environment variables have types, deployments have validation, and your team moves faster.

The magic isn't generating files. It's never having to think about environment variable management again.